package org.ricks.utils;

import org.ricks.common.Assert;
import org.ricks.common.IORuntimeException;
import org.ricks.common.bean.ArrayUtil;
import org.ricks.common.conf.LoadFileName;
import org.ricks.log.Logger;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.Consumer;

/**
 * @author chenwei
 * @Title:
 * @Package
 * @Description:
 * @date 2020/8/717:19
 */
public class FileUtil {

    /**
     * Class文件扩展名
     */
    public static final String CLASS_EXT = FileNameUtil.EXT_CLASS;
    /**
     * Jar文件扩展名
     */
    public static final String JAR_FILE_EXT = FileNameUtil.EXT_JAR;
    /**
     * 在Jar中的路径jar的扩展名形式
     */
    public static final String JAR_PATH_EXT = ".jar!";
    /**
     * 当Path为文件形式时, path会加入一个表示文件的前缀
     */
    public static final String PATH_FILE_PRE = URLUtil.FILE_URL_PREFIX;


    /**
     * 是否为Windows环境
     *
     * @return 是否为Windows环境
     * @since 3.0.9
     */
    public static boolean isWindows() {
        return FileNameUtil.WINDOWS_SEPARATOR == File.separatorChar;
    }



    /**
     * 文件是否为空<br>
     * 目录：里面没有文件时为空 文件：文件大小为0时为空
     *
     * @param file 文件
     * @return 是否为空，当提供非目录时，返回false
     */
    public static boolean isEmpty(File file) {
        if (null == file) {
            return true;
        }

        if (file.isDirectory()) {
            String[] subFiles = file.list();
            return ArrayUtil.isEmpty(subFiles);
        } else if (file.isFile()) {
            return file.length() <= 0;
        }

        return false;
    }

    /**
     * 目录是否为空
     *
     * @param file 目录
     * @return 是否为空，当提供非目录时，返回false
     */
    public static boolean isNotEmpty(File file) {
        return false == isEmpty(file);
    }

    /**
     * 递归遍历目录以及子目录中的所有文件<br>
     * 如果提供file为文件，直接返回过滤结果
     *
     * @param path       当前遍历文件或目录的路径
     * @param fileFilter 文件过滤规则对象，选择要保留的文件，只对文件有效，不过滤目录
     * @return 文件列表
     * @since 3.2.0
     */
    public static List<File> loopFiles(String path, FileFilter fileFilter) {
        return loopFiles(file(path), fileFilter);
    }

    /**
     * 递归遍历目录以及子目录中的所有文件<br>
     * 如果提供file为文件，直接返回过滤结果
     *
     * @param file       当前遍历文件或目录
     * @param fileFilter 文件过滤规则对象，选择要保留的文件，只对文件有效，不过滤目录
     * @return 文件列表
     */
    public static List<File> loopFiles(File file, FileFilter fileFilter) {
        if (null == file || false == file.exists()) {
            return ListUtil.empty();
        }

        final List<File> fileList = new ArrayList<>();
        walkFiles(file, fileList::add);
        return fileList;
    }

    /**
     * 递归遍历目录并处理目录下的文件，可以处理目录或文件：
     * <ul>
     *     <li>非目录则直接调用{@link Consumer}处理</li>
     *     <li>目录则递归调用此方法处理</li>
     * </ul>
     *
     * @param file     文件或目录，文件直接处理
     * @param consumer 文件处理器，只会处理文件
     * @since 5.5.2
     */
    public static void walkFiles(File file, Consumer<File> consumer) {
        if (file.isDirectory()) {
            final File[] subFiles = file.listFiles();
            if (ArrayUtil.isNotEmpty(subFiles)) {
                for (File tmp : subFiles) {
                    walkFiles(tmp, consumer);
                }
            }
        } else {
            consumer.accept(file);
        }
    }



    /**
     * 递归遍历目录以及子目录中的所有文件
     *
     * @param path 当前遍历文件或目录的路径
     * @return 文件列表
     * @since 3.2.0
     */
    public static List<File> loopFiles(String path) {
        return loopFiles(file(path));
    }

    /**
     * 递归遍历目录以及子目录中的所有文件
     *
     * @param file 当前遍历文件
     * @return 文件列表
     */
    public static List<File> loopFiles(File file) {
        return loopFiles(file, null);
    }



    /**
     * 创建File对象，相当于调用new File()，不做任何处理
     *
     * @param path 文件路径
     * @return File
     * @since 4.1.4
     */
    public static File newFile(String path) {
        return new File(path);
    }



    /**
     * 创建File对象<br>
     * 此方法会检查slip漏洞，漏洞说明见http://blog.nsfocus.net/zip-slip-2/
     *
     * @param parent 父目录
     * @param path   文件路径
     * @return File
     */
    public static File file(String parent, String path) {
        return file(new File(parent), path);
    }


    /**
     * 通过多层目录参数创建文件<br>
     * 此方法会检查slip漏洞，漏洞说明见http://blog.nsfocus.net/zip-slip-2/
     *
     * @param directory 父目录
     * @param names     元素名（多层目录名），由外到内依次传入
     * @return the file 文件
     * @since 4.0.6
     */
    public static File file(File directory, String... names) {
        Assert.notNull(directory, "directorydirectory must not be null");
        if (ArrayUtil.isEmpty(names)) {
            return directory;
        }

        File file = directory;
        for (String name : names) {
            if (null != name) {
                file = file(file, name);
            }
        }
        return file;
    }

    /**
     * 通过多层目录创建文件
     * <p>
     * 元素名（多层目录名）
     *
     * @return the file 文件
     * @since 4.0.6
     */
    public static File file(String path) {
        if (null == path) {
            return null;
        }
        return new File(getAbsolutePath(path));
    }

    /**
     * 创建File对象
     *
     * @param uri 文件URI
     * @return File
     */
    public static File file(URI uri) {
        if (uri == null) {
            throw new NullPointerException("File uri is null!");
        }
        return new File(uri);
    }

    /**
     * 创建File对象
     *
     * @param url 文件URL
     * @return File
     */
    public static File file(URL url) {
        return new File(URLUtil.toURI(url));
    }

    /**
     * 获取临时文件路径（绝对路径）
     *
     * @return 临时文件路径
     * @since 4.0.6
     */
    public static String getTmpDirPath() {
        return System.getProperty("java.io.tmpdir");
    }

    /**
     * 获取临时文件目录
     *
     * @return 临时文件目录
     * @since 4.0.6
     */
    public static File getTmpDir() {
        return file(getTmpDirPath());
    }

    /**
     * 获取用户路径（绝对路径）
     *
     * @return 用户路径
     * @since 4.0.6
     */
    public static String getUserHomePath() {
        return System.getProperty("user.home");
    }

    /**
     * 获取用户目录
     *
     * @return 用户目录
     * @since 4.0.6
     */
    public static File getUserHomeDir() {
        return file(getUserHomePath());
    }

    /**
     * 判断文件是否存在，如果path为null，则返回false
     *
     * @param path 文件路径
     * @return 如果存在返回true
     */
    public static boolean exist(String path) {
        return (null != path) && file(path).exists();
    }

    /**
     * 判断文件是否存在，如果file为null，则返回false
     *
     * @param file 文件
     * @return 如果存在返回true
     */
    public static boolean exist(File file) {
        return (null != file) && file.exists();
    }

    /**
     * 是否存在匹配文件
     *
     * @param directory 文件夹路径
     * @param regexp    文件夹中所包含文件名的正则表达式
     * @return 如果存在匹配文件返回true
     */
    public static boolean exist(String directory, String regexp) {
        final File file = new File(directory);
        if (false == file.exists()) {
            return false;
        }

        final String[] fileList = file.list();
        if (fileList == null) {
            return false;
        }

        for (String fileName : fileList) {
            if (fileName.matches(regexp)) {
                return true;
            }

        }
        return false;
    }

    /**
     * 指定文件最后修改时间
     *
     * @param file 文件
     * @return 最后修改时间
     */
    public static Date lastModifiedTime(File file) {
        if (false == exist(file)) {
            return null;
        }

        return new Date(file.lastModified());
    }

    /**
     * 指定路径文件最后修改时间
     *
     * @param path 绝对路径
     * @return 最后修改时间
     */
    public static Date lastModifiedTime(String path) {
        return lastModifiedTime(new File(path));
    }



    /**
     * 给定文件或目录的最后修改时间是否晚于给定时间
     *
     * @param file      文件或目录
     * @param reference 参照文件
     * @return 是否晚于给定时间
     */
    public static boolean newerThan(File file, File reference) {
        if (null == reference || false == reference.exists()) {
            return true;// 文件一定比一个不存在的文件新
        }
        return newerThan(file, reference.lastModified());
    }

    /**
     * 给定文件或目录的最后修改时间是否晚于给定时间
     *
     * @param file       文件或目录
     * @param timeMillis 做为对比的时间
     * @return 是否晚于给定时间
     */
    public static boolean newerThan(File file, long timeMillis) {
        if (null == file || false == file.exists()) {
            return false;// 不存在的文件一定比任何时间旧
        }
        return file.lastModified() > timeMillis;
    }

    /**
     * 创建文件及其父目录，如果这个文件存在，直接返回这个文件<br>
     * 此方法不对File对象类型做判断，如果File不存在，无法判断其类型
     *
     * @param fullFilePath 文件的全路径，使用POSIX风格
     * @return 文件，若路径为null，返回null
     * @throws IORuntimeException IO异常
     */
    public static File touch(String fullFilePath) throws IORuntimeException {
        return fullFilePath == null ? null : touch(file(fullFilePath));
    }

    /**
     * 创建文件及其父目录，如果这个文件存在，直接返回这个文件<br>
     * 此方法不对File对象类型做判断，如果File不存在，无法判断其类型
     *
     * @param file 文件对象
     * @return 文件，若路径为null，返回null
     * @throws IORuntimeException IO异常
     */
    public static File touch(File file) throws IORuntimeException {
        if (null == file) {
            return null;
        }
        if (false == file.exists()) {
            mkParentDirs(file);
            try {
                //noinspection ResultOfMethodCallIgnored
                file.createNewFile();
            } catch (Exception e) {
                throw new IORuntimeException(e);
            }
        }
        return file;
    }

    /**
     * 创建文件及其父目录，如果这个文件存在，直接返回这个文件<br>
     * 此方法不对File对象类型做判断，如果File不存在，无法判断其类型
     *
     * @param parent 父文件对象
     * @param path   文件路径
     * @return File
     * @throws IORuntimeException IO异常
     */
    public static File touch(File parent, String path) throws IORuntimeException {
        return touch(file(parent, path));
    }

    /**
     * 创建文件及其父目录，如果这个文件存在，直接返回这个文件<br>
     * 此方法不对File对象类型做判断，如果File不存在，无法判断其类型
     *
     * @param parent 父文件对象
     * @param path   文件路径
     * @return File
     * @throws IORuntimeException IO异常
     */
    public static File touch(String parent, String path) throws IORuntimeException {
        return touch(file(parent, path));
    }

    /**
     * 创建所给文件或目录的父目录
     *
     * @param file 文件或目录
     * @return 父目录
     */
    public static File mkParentDirs(File file) {
        final File parentFile = file.getParentFile();
        if (null != parentFile && false == parentFile.exists()) {
            //noinspection ResultOfMethodCallIgnored
            parentFile.mkdirs();
        }
        return parentFile;
    }

    /**
     * 创建父文件夹，如果存在直接返回此文件夹
     *
     * @param path 文件夹路径，使用POSIX格式，无论哪个平台
     * @return 创建的目录
     */
    public static File mkParentDirs(String path) {
        if (path == null) {
            return null;
        }
        return mkParentDirs(file(path));
    }

    /**
     * 删除文件或者文件夹<br>
     * 路径如果为相对路径，会转换为ClassPath路径！ 注意：删除文件夹时不会判断文件夹是否为空，如果不空则递归删除子文件或文件夹<br>
     * 某个文件删除失败会终止删除操作
     *
     * @param fullFileOrDirPath 文件或者目录的路径
     * @return 成功与否
     * @throws IORuntimeException IO异常
     */
    public static boolean del(String fullFileOrDirPath) throws IORuntimeException {
        return del(file(fullFileOrDirPath));
    }

    /**
     * 删除文件或者文件夹<br>
     * 注意：删除文件夹时不会判断文件夹是否为空，如果不空则递归删除子文件或文件夹<br>
     * 某个文件删除失败会终止删除操作
     *
     * @param file 文件对象
     * @return 成功与否
     * @throws IORuntimeException IO异常
     */
    public static boolean del(File file) throws IORuntimeException {
        if (file == null || false == file.exists()) {
            // 如果文件不存在或已被删除，此处返回true表示删除成功
            return true;
        }

        if (file.isDirectory()) {
            // 清空目录下所有文件和目录
            boolean isOk = clean(file);
            if (false == isOk) {
                return false;
            }
        }

        // 删除文件或清空后的目录
        return file.delete();
    }

    /**
     * 清空文件夹<br>
     * 注意：清空文件夹时不会判断文件夹是否为空，如果不空则递归删除子文件或文件夹<br>
     * 某个文件删除失败会终止删除操作
     *
     * @param dirPath 文件夹路径
     * @return 成功与否
     * @throws IORuntimeException IO异常
     * @since 4.0.8
     */
    public static boolean clean(String dirPath) throws IORuntimeException {
        return clean(file(dirPath));
    }

    /**
     * 清空文件夹<br>
     * 注意：清空文件夹时不会判断文件夹是否为空，如果不空则递归删除子文件或文件夹<br>
     * 某个文件删除失败会终止删除操作
     *
     * @param directory 文件夹
     * @return 成功与否
     * @throws IORuntimeException IO异常
     * @since 3.0.6
     */
    public static boolean clean(File directory) throws IORuntimeException {
        if (directory == null || directory.exists() == false || false == directory.isDirectory()) {
            return true;
        }

        final File[] files = directory.listFiles();
        if (null != files) {
            boolean isOk;
            for (File childFile : files) {
                isOk = del(childFile);
                if (isOk == false) {
                    // 删除一个出错则本次删除任务失败
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 清理空文件夹<br>
     * 此方法用于递归删除空的文件夹，不删除文件<br>
     * 如果传入的文件夹本身就是空的，删除这个文件夹
     *
     * @param directory 文件夹
     * @return 成功与否
     * @throws IORuntimeException IO异常
     * @since 4.5.5
     */
    public static boolean cleanEmpty(File directory) throws IORuntimeException {
        if (directory == null || false == directory.exists() || false == directory.isDirectory()) {
            return true;
        }

        final File[] files = directory.listFiles();
        if (ArrayUtil.isEmpty(files)) {
            // 空文件夹则删除之
            return directory.delete();
        }

        for (File childFile : files) {
            cleanEmpty(childFile);
        }
        return true;
    }

    /**
     * 创建文件夹，如果存在直接返回此文件夹<br>
     * 此方法不对File对象类型做判断，如果File不存在，无法判断其类型
     *
     * @param dirPath 文件夹路径，使用POSIX格式，无论哪个平台
     * @return 创建的目录
     */
    public static File mkdir(String dirPath) {
        if (dirPath == null) {
            return null;
        }
        final File dir = file(dirPath);
        return mkdir(dir);
    }

    /**
     * 创建文件夹，会递归自动创建其不存在的父文件夹，如果存在直接返回此文件夹<br>
     * 此方法不对File对象类型做判断，如果File不存在，无法判断其类型
     *
     * @param dir 目录
     * @return 创建的目录
     */
    public static File mkdir(File dir) {
        if (dir == null) {
            return null;
        }
        if (false == dir.exists()) {
            dir.mkdirs();
        }
        return dir;
    }

    /**
     * 创建临时文件<br>
     * 创建后的文件名为 prefix[Randon].tmp
     *
     * @param dir 临时文件创建的所在目录
     * @return 临时文件
     * @throws IORuntimeException IO异常
     */
    public static File createTempFile(File dir) throws IORuntimeException {
        return createTempFile("hutool", null, dir, true);
    }

    /**
     * 创建临时文件<br>
     * 创建后的文件名为 prefix[Randon].tmp
     *
     * @param dir       临时文件创建的所在目录
     * @param isReCreat 是否重新创建文件（删掉原来的，创建新的）
     * @return 临时文件
     * @throws IORuntimeException IO异常
     */
    public static File createTempFile(File dir, boolean isReCreat) throws IORuntimeException {
        return createTempFile("hutool", null, dir, isReCreat);
    }

    /**
     * 创建临时文件<br>
     * 创建后的文件名为 prefix[Randon].suffix From com.jodd.io.FileUtil
     *
     * @param prefix    前缀，至少3个字符
     * @param suffix    后缀，如果null则使用默认.tmp
     * @param dir       临时文件创建的所在目录
     * @param isReCreat 是否重新创建文件（删掉原来的，创建新的）
     * @return 临时文件
     * @throws IORuntimeException IO异常
     */
    public static File createTempFile(String prefix, String suffix, File dir, boolean isReCreat) throws IORuntimeException {
        int exceptionsCount = 0;
        while (true) {
            try {
                File file = File.createTempFile(prefix, suffix, dir).getCanonicalFile();
                if (isReCreat) {
                    //noinspection ResultOfMethodCallIgnored
                    file.delete();
                    //noinspection ResultOfMethodCallIgnored
                    file.createNewFile();
                }
                return file;
            } catch (IOException ioex) { // fixes java.io.WinNTFileSystem.createFileExclusively access denied
                if (++exceptionsCount >= 50) {
                    throw new IORuntimeException(ioex);
                }
            }
        }
    }



    /**
     * 获取规范的绝对路径
     *
     * @param file 文件
     * @return 规范绝对路径，如果传入file为null，返回null
     * @since 4.1.4
     */
    public static String getCanonicalPath(File file) {
        if (null == file) {
            return null;
        }
        try {
            return file.getCanonicalPath();
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }


    /**
     * 获取标准的绝对路径
     *
     * @param file 文件
     * @return 绝对路径
     */
    public static String getAbsolutePath(File file) {
        if (file == null) {
            return null;
        }

        try {
            return file.getCanonicalPath();
        } catch (IOException e) {
            return file.getAbsolutePath();
        }
    }


    /**
     * 获取绝对路径，相对于ClassPath的目录<br>
     * 如果给定就是绝对路径，则返回原路径，原路径把所有\替换为/<br>
     * 兼容Spring风格的路径表示，例如：classpath:config/example.setting也会被识别后转换
     *
     * @param path 相对路径
     * @return 绝对路径
     */
    public static String getAbsolutePath(String path) {
        return getAbsolutePath(path, null);
    }


    /**
     * 获取绝对路径<br>
     * 此方法不会判定给定路径是否有效（文件或目录存在）
     *
     * @param path      相对路径
     * @param baseClass 相对路径所相对的类
     * @return 绝对路径
     */
    public static String getAbsolutePath(String path, Class<?> baseClass) {
        String normalPath;
        if (path == null) {
            normalPath = StrUtil.EMPTY;
        } else {
            normalPath = normalize(path);
            if (isAbsolutePath(normalPath)) {
                // 给定的路径已经是绝对路径了
                return normalPath;
            }
        }

        // 相对于ClassPath路径
        final URL url = ResourceUtil.getResource(normalPath, baseClass);
        if (null != url) {
            // 对于jar中文件包含file:前缀，需要去掉此类前缀，在此做标准化，since 3.0.8 解决中文或空格路径被编码的问题
            return FileUtil.normalize(URLUtil.getDecodedPath(url));
        }

        // 如果资源不存在，则返回一个拼接的资源绝对路径
        final String classPath = ClassUtil.getClassPath();
        if (null == classPath) {
            // throw new NullPointerException("ClassPath is null !");
            // 在jar运行模式中，ClassPath有可能获取不到，此时返回原始相对路径（此时获取的文件为相对工作目录）
            return path;
        }

        // 资源不存在的情况下使用标准化路径有问题，使用原始路径拼接后标准化路径
        return normalize(classPath.concat(Objects.requireNonNull(path)));
    }


    /**
     * 给定路径已经是绝对路径<br>
     * 此方法并没有针对路径做标准化，建议先执行{@link #(String)}方法标准化路径后判断
     *
     * @param path 需要检查的Path
     * @return 是否已经是绝对路径
     */
    public static boolean isAbsolutePath(String path) {
        if (StrUtil.isEmpty(path)) {
            return false;
        }

        // 给定的路径已经是绝对路径了
        return StrUtil.C_SLASH == path.charAt(0) || path.matches("^[a-zA-Z]:([/\\\\].*)?");
    }

    /**
     * 判断是否为目录，如果path为null，则返回false
     *
     * @param path 文件路径
     * @return 如果为目录true
     */
    public static boolean isDirectory(String path) {
        return (null != path) && file(path).isDirectory();
    }

    /**
     * 判断是否为目录，如果file为null，则返回false
     *
     * @param file 文件
     * @return 如果为目录true
     */
    public static boolean isDirectory(File file) {
        return (null != file) && file.isDirectory();
    }

    /**
     * 判断是否为文件，如果path为null，则返回false
     *
     * @param path 文件路径
     * @return 如果为文件true
     */
    public static boolean isFile(String path) {
        return (null != path) && file(path).isFile();
    }

    /**
     * 判断是否为文件，如果file为null，则返回false
     *
     * @param file 文件
     * @return 如果为文件true
     */
    public static boolean isFile(File file) {
        return (null != file) && file.isFile();
    }




    // -----------------------------------------------------------------------



    /**
     * 文件路径是否相同<br>
     * 取两个文件的绝对路径比较，在Windows下忽略大小写，在Linux下不忽略。
     *
     * @param file1 文件1
     * @param file2 文件2
     * @return 文件路径是否相同
     * @since 3.0.9
     */
    public static boolean pathEquals(File file1, File file2) {
        if (isWindows()) {
            // Windows环境
            try {
                if (StrUtil.equalsIgnoreCase(file1.getCanonicalPath(), file2.getCanonicalPath())) {
                    return true;
                }
            } catch (Exception e) {
                if (StrUtil.equalsIgnoreCase(file1.getAbsolutePath(), file2.getAbsolutePath())) {
                    return true;
                }
            }
        } else {
            // 类Unix环境
            try {
                if (StrUtil.equals(file1.getCanonicalPath(), file2.getCanonicalPath())) {
                    return true;
                }
            } catch (Exception e) {
                if (StrUtil.equals(file1.getAbsolutePath(), file2.getAbsolutePath())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获得最后一个文件路径分隔符的位置
     *
     * @param filePath 文件路径
     * @return 最后一个文件路径分隔符的位置
     */
    public static int lastIndexOfSeparator(String filePath) {
        if (StrUtil.isNotEmpty(filePath)) {
            int i = filePath.length();
            char c;
            while (--i >= 0) {
                c = filePath.charAt(i);
                if (CharUtil.isFileSeparator(c)) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 判断文件是否被改动<br>
     * 如果文件对象为 null 或者文件不存在，被视为改动
     *
     * @param file           文件对象
     * @param lastModifyTime 上次的改动时间
     * @return 是否被改动
     */
    public static boolean isModifed(File file, long lastModifyTime) {
        if (null == file || false == file.exists()) {
            return true;
        }
        return file.lastModified() != lastModifyTime;
    }



    // -------------------------------------------------------------------------------------------- name start

    /**
     * 返回文件名
     *
     * @param file 文件
     * @return 文件名
     * @see FileNameUtil#getName(File)
     * @since 4.1.13
     */
    public static String getName(File file) {
        return FileNameUtil.getName(file);
    }

    /**
     * 返回文件名
     *
     * @param filePath 文件
     * @return 文件名
     * @see FileNameUtil#getName(String)
     * @since 4.1.13
     */
    public static String getName(String filePath) {
        return FileNameUtil.getName(filePath);
    }

    /**
     * 获取文件后缀名，扩展名不带“.”
     *
     * @param file 文件
     * @return 扩展名
     * @see FileNameUtil#getSuffix(File)
     * @since 5.3.8
     */
    public static String getSuffix(File file) {
        return FileNameUtil.getSuffix(file);
    }

    /**
     * 获得文件后缀名，扩展名不带“.”
     *
     * @param fileName 文件名
     * @return 扩展名
     * @see FileNameUtil#getSuffix(String)
     * @since 5.3.8
     */
    public static String getSuffix(String fileName) {
        return FileNameUtil.getSuffix(fileName);
    }

    /**
     * 返回主文件名
     *
     * @param file 文件
     * @return 主文件名
     * @see FileNameUtil#getPrefix(File)
     * @since 5.3.8
     */
    public static String getPrefix(File file) {
        return FileNameUtil.getPrefix(file);
    }

    /**
     * 返回主文件名
     *
     * @param fileName 完整文件名
     * @return 主文件名
     * @see FileNameUtil#getPrefix(String)
     * @since 5.3.8
     */
    public static String getPrefix(String fileName) {
        return FileNameUtil.getPrefix(fileName);
    }

    /**
     * 返回主文件名
     *
     * @param file 文件
     * @return 主文件名
     * @see FileNameUtil#mainName(File)
     */
    public static String mainName(File file) {
        return FileNameUtil.mainName(file);
    }

    /**
     * 返回主文件名
     *
     * @param fileName 完整文件名
     * @return 主文件名
     * @see FileNameUtil#mainName(String)
     */
    public static String mainName(String fileName) {
        return FileNameUtil.mainName(fileName);
    }

    /**
     * 获取文件扩展名（后缀名），扩展名不带“.”
     *
     * @param file 文件
     * @return 扩展名
     * @see FileNameUtil#extName(File)
     */
    public static String extName(File file) {
        return FileNameUtil.extName(file);
    }

    /**
     * 获得文件的扩展名（后缀名），扩展名不带“.”
     *
     * @param fileName 文件名
     * @return 扩展名
     * @see FileNameUtil#extName(String)
     */
    public static String extName(String fileName) {
        return FileNameUtil.extName(fileName);
    }
    // -------------------------------------------------------------------------------------------- name end

    /**
     * 判断文件路径是否有指定后缀，忽略大小写<br>
     * 常用语判断扩展名
     *
     * @param file   文件或目录
     * @param suffix 后缀
     * @return 是否有指定后缀
     */
    public static boolean pathEndsWith(File file, String suffix) {
        return file.getPath().toLowerCase().endsWith(suffix);
    }



    // -------------------------------------------------------------------------------------------- in start

    /**
     * 获得输入流
     *
     * @param file 文件
     * @return 输入流
     * @throws IORuntimeException 文件未找到
     */
    public static BufferedInputStream getInputStream(File file) throws IORuntimeException {
        return IoUtil.toBuffered(IoUtil.toStream(file));
    }

    /**
     * 获得输入流
     *
     * @param path 文件路径
     * @return 输入流
     * @throws IORuntimeException 文件未找到
     */
    public static BufferedInputStream getInputStream(String path)  {
        return getInputStream(file(path));
    }


    /**
     * 获得一个文件读取器
     *
     * @param file 文件
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedReader getUtf8Reader(File file) throws IORuntimeException {
        return getReader(file, CharsetUtil.CHARSET_UTF_8);
    }

    /**
     * 获得一个文件读取器
     *
     * @param path 文件路径
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedReader getUtf8Reader(String path) throws IORuntimeException {
        return getReader(path, CharsetUtil.CHARSET_UTF_8);
    }

    /**
     * 获得一个文件读取器
     *
     * @param file        文件
     * @param charsetName 字符集
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedReader getReader(File file, String charsetName) throws IORuntimeException {
        return IoUtil.getReader(getInputStream(file), charsetName);
    }

    /**
     * 获得一个文件读取器
     *
     * @param file    文件
     * @param charset 字符集
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedReader getReader(File file, Charset charset) throws IORuntimeException {
        return IoUtil.getReader(getInputStream(file), charset);
    }

    /**
     * 获得一个文件读取器
     *
     * @param path        绝对路径
     * @param charsetName 字符集
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedReader getReader(String path, String charsetName) throws IORuntimeException {
        return getReader(file(path), charsetName);
    }

    /**
     * 获得一个文件读取器
     *
     * @param path    绝对路径
     * @param charset 字符集
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedReader getReader(String path, Charset charset) throws IORuntimeException {
        return getReader(file(path), charset);
    }

    // -------------------------------------------------------------------------------------------- in end



    /**
     * 读取文件内容
     *
     * @param url     文件URL
     * @param charset 字符集
     * @return 内容
     * @throws IORuntimeException IO异常
     */
    public static String readString(URL url, String charset) throws IORuntimeException {
        if (url == null) {
            throw new NullPointerException("Empty url provided!");
        }

        InputStream in = null;
        try {
            in = url.openStream();
            return IoUtil.read(in, charset);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        } finally {
            IoUtil.close(in);
        }
    }



    // -------------------------------------------------------------------------------------------- out start

    /**
     * 获得一个输出流对象
     *
     * @param file 文件
     * @return 输出流对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedOutputStream getOutputStream(File file) throws IORuntimeException {
        final OutputStream out;
        try {
            out = new FileOutputStream(touch(file));
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        return IoUtil.toBuffered(out);
    }

    /**
     * 获得一个输出流对象
     *
     * @param path 输出到的文件路径，绝对路径
     * @return 输出流对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedOutputStream getOutputStream(String path) throws IORuntimeException {
        return getOutputStream(touch(path));
    }



    /**
     * 获取当前系统的换行分隔符
     *
     * <pre>
     * Windows: \r\n
     * Mac: \r
     * Linux: \n
     * </pre>
     *
     * @return 换行符
     * @since 4.0.5
     */
    public static String getLineSeparator() {
        return System.lineSeparator();
        // return System.getProperty("line.separator");
    }

    // -------------------------------------------------------------------------------------------- out end





    /**
     * 清除文件名中的在Windows下不支持的非法字符，包括： \ / : * ? " &lt; &gt; |
     *
     * @param fileName 文件名（必须不包括路径，否则路径符将被替换）
     * @return 清理后的文件名
     * @see FileNameUtil#cleanInvalid(String)
     * @since 3.3.1
     */
    public static String cleanInvalid(String fileName) {
        return FileNameUtil.cleanInvalid(fileName);
    }

    /**
     * 文件名中是否包含在Windows下不支持的非法字符，包括： \ / : * ? " &lt; &gt; |
     *
     * @param fileName 文件名（必须不包括路径，否则路径符将被替换）
     * @return 是否包含非法字符
     * @see FileNameUtil#containsInvalid(String)
     * @since 3.3.1
     */
    public static boolean containsInvalid(String fileName) {
        return FileNameUtil.containsInvalid(fileName);
    }

    /**
     * 获取Web项目下的web root路径<br>
     * 原理是首先获取ClassPath路径，由于在web项目中ClassPath位于 WEB-INF/classes/下，故向上获取两级目录即可。
     *
     * @return web root路径
     * @since 4.0.13
     */
    public static File getWebRoot() {
        final String classPath = ClassUtil.getClassPath();
        if (StrUtil.isNotBlank(classPath)) {
            return getParent(file(classPath), 2);
        }
        return null;
    }

    /**
     * 获取指定层级的父路径
     *
     * <pre>
     * getParent("d:/aaa/bbb/cc/ddd", 0) -》 "d:/aaa/bbb/cc/ddd"
     * getParent("d:/aaa/bbb/cc/ddd", 2) -》 "d:/aaa/bbb"
     * getParent("d:/aaa/bbb/cc/ddd", 4) -》 "d:/"
     * getParent("d:/aaa/bbb/cc/ddd", 5) -》 null
     * </pre>
     *
     * @param filePath 目录或文件路径
     * @param level    层级
     * @return 路径File，如果不存在返回null
     * @since 4.1.2
     */
    public static String getParent(String filePath, int level) {
        final File parent = getParent(file(filePath), level);
        try {
            return null == parent ? null : parent.getCanonicalPath();
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }

    /**
     * 获取指定层级的父路径
     *
     * <pre>
     * getParent(file("d:/aaa/bbb/cc/ddd", 0)) -》 "d:/aaa/bbb/cc/ddd"
     * getParent(file("d:/aaa/bbb/cc/ddd", 2)) -》 "d:/aaa/bbb"
     * getParent(file("d:/aaa/bbb/cc/ddd", 4)) -》 "d:/"
     * getParent(file("d:/aaa/bbb/cc/ddd", 5)) -》 null
     * </pre>
     *
     * @param file  目录或文件
     * @param level 层级
     * @return 路径File，如果不存在返回null
     * @since 4.1.2
     */
    public static File getParent(File file, int level) {
        if (level < 1 || null == file) {
            return file;
        }

        File parentFile;
        try {
            parentFile = file.getCanonicalFile().getParentFile();
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        if (1 == level) {
            return parentFile;
        }
        return getParent(parentFile, level - 1);
    }

    /**
     * 检查父完整路径是否为自路径的前半部分，如果不是说明不是子路径，可能存在slip注入。
     * <p>
     * 见http://blog.nsfocus.net/zip-slip-2/
     *
     * @param parentFile 父文件或目录
     * @param file       子文件或目录
     * @return 子文件或目录
     * @throws IllegalArgumentException 检查创建的子文件不在父目录中抛出此异常
     */
    public static File checkSlip(File parentFile, File file) throws IllegalArgumentException {
        if (null != parentFile && null != file) {
            String parentCanonicalPath;
            String canonicalPath;
            try {
                parentCanonicalPath = parentFile.getCanonicalPath();
                canonicalPath = file.getCanonicalPath();
            } catch (IOException e) {
                throw new IORuntimeException(e);
            }
            if (false == canonicalPath.startsWith(parentCanonicalPath)) {
                throw new IllegalArgumentException("New file is outside of the parent dir: " + file.getName());
            }
        }
        return file;
    }


    /**
     * 判断给定的目录是否为给定文件或文件夹的子目录
     *
     * @param parent 父目录
     * @param sub    子目录
     * @return 子目录是否为父目录的子目录
     * @since 4.5.4
     */
    public static boolean isSub(File parent, File sub) {
        Assert.notNull(parent);
        Assert.notNull(sub);
        return sub.toPath().startsWith(parent.toPath());
    }







    /**
     * 根目录为classpath
     *
     * @param ins
     * @return
     * @throws IOException
     */
    public static String readFile(InputStream ins) throws IOException {
//        InputStream ins = getFileInputStream(path);
        if (Objects.isNull(ins)) return "";
        InputStreamReader in = new InputStreamReader(ins);
        StringBuilder res = new StringBuilder();

        int c;
        while ((c = in.read()) != -1) {
            res.append((char) c);
        }
        in.close();

        return res.toString();
    }


    /**
     * 获取指定文件（classpath下的）的inputStream
     *
     * @param path
     * @return
     * @throws IOException
     */
    public static InputStream getFileInputStream(String path)  {
        try {
            return new FileInputStream(getFile(path));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根目录为classpath。
     *
     * @param path
     * @param content
     * @throws IOException
     */
    public static void writeFile(String path, String content) {
        try {
            File file = touch(getFile((path)));
            FileWriter out = new FileWriter(file);
            out.write(content);
            out.flush();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 此拦截仅仅在idea 开发环境生效，外网jar部署 没啥用
     * @param path
     * @return boolean
     * @throws
     * @author chenwei
     * @date 2020/8/19 20:33
     */
    public static boolean judgeFileExist(String path) {
        URL url = Thread.currentThread().getContextClassLoader().getResource("");
        File file = new File(url.getPath() + path);
        return Objects.nonNull(file.getParentFile()) && file.getParentFile().exists() && file.exists();
    }

    public static File getFile(String path) {
        File file = null;
        try {
            URL url = Thread.currentThread().getContextClassLoader().getResource("");
            String filePath = "";
            if(url != null) {
                filePath = url.getPath();
                file = new File(filePath, path);
                if(file.exists()) return file;
            }
            filePath = System.getProperty("user.dir");
            file = new File(filePath, path);
        }catch (Exception e){
            e.printStackTrace();
            Logger.error("加载配置文件失败！" );
        }
        return file;
    }

    public static void getFiles(String path,Map<String,String> source) {
        try {
            File file = new File(path);
            if(file.isDirectory()) {
                File[] files = file.listFiles();
                for (int i = 0; i < files.length; i++) {
                    getFiles(files[i].getPath(),source);
                }
            } else {
                String basePath = "" ;
                if(file.getPath().indexOf("static") >= 0) {
                    basePath = file.getPath().substring(file.getPath().indexOf("static")).replaceAll("\\\\","/");
                } else if(file.getPath().indexOf("upload") >= 0) {
                    basePath = file.getPath().substring(file.getPath().indexOf("upload")).replaceAll("\\\\","/");
                }
                source.put(basePath,readFile(file));
            }
        }catch (Exception e){
            e.printStackTrace();
            Logger.error("加载配置文件失败！" );
        }
    }

    public static String getPath() {
        String path = FileUtil.class.getProtectionDomain().getCodeSource().getLocation().getPath();
        if(System.getProperty("os.name").contains("dows")) {
            path = path.substring(1,path.length());
        }
        if(path.contains("jar")) {
            path = path.substring(0,path.lastIndexOf("."));
            return path.substring(0,path.lastIndexOf("/"));
        }
        return path.replace("target/classes/", "");
    }

    /**传入txt路径读取txt文件
     * @param path
     * @return 返回读取到的内容
     */
    public static String readFile(String path) {
        File file = getFile(path);
        FileInputStream fileInputStream;
        InputStreamReader inputStreamReader;
        BufferedReader bufferedReader;
        if(null != file && file.isFile() && file.exists()){
            try {
                fileInputStream = new FileInputStream(file);
                inputStreamReader = new InputStreamReader(fileInputStream);
                bufferedReader = new BufferedReader(inputStreamReader);

                StringBuffer sb = new StringBuffer();
                String text = "";
                while((text = bufferedReader.readLine()) != null){
                    sb.append(text).append("\n");
                }
                bufferedReader.close();
                inputStreamReader.close();
                fileInputStream.close();
                return sb.toString();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }



    /**传入txt路径读取txt文件
     * @param file
     * @return 返回读取到的内容
     */
    public static String readFile(File file) {
        StringBuilder builder = new StringBuilder();
        FileInputStream fileInputStream;
        InputStreamReader inputStreamReader;
        BufferedReader bufferedReader;
        if(null != file && file.isFile() && file.exists()){
            try {
                fileInputStream = new FileInputStream(file);
                inputStreamReader = new InputStreamReader(fileInputStream);
                bufferedReader = new BufferedReader(inputStreamReader);

                StringBuffer sb = new StringBuffer();
                String text = "";
                while((text = bufferedReader.readLine()) != null){
                    builder.append(text);
                }
                bufferedReader.close();
                inputStreamReader.close();
                fileInputStream.close();
                return sb.toString();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return builder.toString();
    }

    public static String readStatic(String path) {
        StringBuilder builder = new StringBuilder();
        File file = getFile(path);
        FileInputStream fileInputStream;
        InputStreamReader inputStreamReader;
        BufferedReader bufferedReader;
        if(null != file && file.isFile() && file.exists()){
            try {
                fileInputStream = new FileInputStream(file);
                inputStreamReader = new InputStreamReader(fileInputStream);
                bufferedReader = new BufferedReader(inputStreamReader);

                StringBuffer sb = new StringBuffer();
                String text = "";
                while((text = bufferedReader.readLine()) != null){
                    sb.append(text);
                }
                bufferedReader.close();
                inputStreamReader.close();
                fileInputStream.close();
                return sb.toString();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return builder.toString();
    }

    // 判断是否以及包含loadFileName
    public static boolean hasLoadFileName(List<LoadFileName> loadFileNameList, LoadFileName loadFileName) {
        for (LoadFileName file : loadFileNameList) {
            if (file.getFileName().equals(loadFileName.getFileName())) {
                return true;
            }
        }
        return false;
    }


    /**
     * 修复路径<br>
     * 如果原路径尾部有分隔符，则保留为标准分隔符（/），否则不保留
     * <ol>
     * <li>1. 统一用 /</li>
     * <li>2. 多个 / 转换为一个 /</li>
     * <li>3. 去除两边空格</li>
     * <li>4. .. 和 . 转换为绝对路径，当..多于已有路径时，直接返回根路径</li>
     * </ol>
     * <p>
     * 栗子：
     *
     * <pre>
     * "/foo//" =》 "/foo/"
     * "/foo/./" =》 "/foo/"
     * "/foo/../bar" =》 "/bar"
     * "/foo/../bar/" =》 "/bar/"
     * "/foo/../bar/../baz" =》 "/baz"
     * "/../" =》 "/"
     * "foo/bar/.." =》 "foo"
     * "foo/../bar" =》 "bar"
     * "foo/../../bar" =》 "bar"
     * "//server/foo/../bar" =》 "/server/bar"
     * "//server/../bar" =》 "/bar"
     * "C:\\foo\\..\\bar" =》 "C:/bar"
     * "C:\\..\\bar" =》 "C:/bar"
     * "~/foo/../bar/" =》 "~/bar/"
     * "~/../bar" =》 "bar"
     * </pre>
     *
     * @param path 原路径
     * @return 修复后的路径
     */
    public static String normalize(String path) {
        if (path == null) {
            return null;
        }


        // 兼容Spring风格的ClassPath路径，去除前缀，不区分大小写
        String pathToUse = StrUtil.removePrefixIgnoreCase(path, URLUtil.CLASSPATH_URL_PREFIX);
        // 去除file:前缀
        pathToUse = StrUtil.removePrefixIgnoreCase(pathToUse, URLUtil.FILE_URL_PREFIX);

        // 识别home目录形式，并转换为绝对路径
        if (pathToUse.startsWith("~")) {
            pathToUse = pathToUse.replace("~", getUserHomePath());
        }

        // 统一使用斜杠
        pathToUse = pathToUse.replaceAll("[/\\\\]+", StrUtil.SLASH).trim();
        //兼容Windows下的共享目录路径（原始路径如果以\\开头，则保留这种路径）
        if (path.startsWith("\\\\")) {
            pathToUse = "\\" + pathToUse;
        }

        String prefix = "";
        int prefixIndex = pathToUse.indexOf(StrUtil.COLON);
        if (prefixIndex > -1) {
            // 可能Windows风格路径
            prefix = pathToUse.substring(0, prefixIndex + 1);
            if (StrUtil.startWith(prefix, StrUtil.C_SLASH)) {
                // 去除类似于/C:这类路径开头的斜杠
                prefix = prefix.substring(1);
            }
            if (false == prefix.contains(StrUtil.SLASH)) {
                pathToUse = pathToUse.substring(prefixIndex + 1);
            } else {
                // 如果前缀中包含/,说明非Windows风格path
                prefix = StrUtil.EMPTY;
            }
        }
        if (pathToUse.startsWith(StrUtil.SLASH)) {
            prefix += StrUtil.SLASH;
            pathToUse = pathToUse.substring(1);
        }

        List<String> pathList = StrUtil.split(pathToUse, StrUtil.C_SLASH);
        List<String> pathElements = new LinkedList<>();
        int tops = 0;

        String element;
        for (int i = pathList.size() - 1; i >= 0; i--) {
            element = pathList.get(i);
            // 只处理非.的目录，即只处理非当前目录
            if (false == StrUtil.DOT.equals(element)) {
                if (StrUtil.DOUBLE_DOT.equals(element)) {
                    tops++;
                } else {
                    if (tops > 0) {
                        // 有上级目录标记时按照个数依次跳过
                        tops--;
                    } else {
                        // Normal path element found.
                        pathElements.add(0, element);
                    }
                }
            }
        }

        return prefix + CollUtil.join(pathElements, StrUtil.SLASH);
    }

//    public static class Pipe implements Runnable {
//        private final InputStream in;
//        private final OutputStream out;
//        private final long sleep;
//        private ByteArrayOutputStream snoop;
//        private long totalWritten;
//        private Throwable thrown;
//        private boolean halt;
//        private final boolean closeInput;
//        private final boolean closeOutput;
//        private boolean finishStream;
//        private boolean done;
//
//        Pipe(InputStream in, OutputStream out) {
//            this(in, out, 100L, false, false);
//        }
//
//        Pipe(InputStream in, OutputStream out, long sleep, boolean closeInput, boolean closeOutput) {
//            LangUtil.throwIaxIfNull(in, "in");
//            LangUtil.throwIaxIfNull(out, "out");
//            this.in = in;
//            this.out = out;
//            this.closeInput = closeInput;
//            this.closeOutput = closeOutput;
//            this.sleep = Math.min(0L, Math.max(60000L, sleep));
//        }
//
//        public void setSnoop(ByteArrayOutputStream snoop) {
//            this.snoop = snoop;
//        }
//
//        public void run() {
//            this.totalWritten = 0L;
//            if (!this.halt) {
//                try {
//                    byte[] buf = new byte[4096];
//
//                    for(int count = this.in.read(buf, 0, 4096); this.halt && this.finishStream && 0 < count || !this.halt && -1 != count; count = this.in.read(buf, 0, 4096)) {
//                        this.out.write(buf, 0, count);
//                        ByteArrayOutputStream mySnoop = this.snoop;
//                        if (null != mySnoop) {
//                            mySnoop.write(buf, 0, count);
//                        }
//
//                        this.totalWritten += (long)count;
//                        if (this.halt && !this.finishStream) {
//                            break;
//                        }
//
//                        if (!this.halt && 0L < this.sleep) {
//                            Thread.sleep(this.sleep);
//                        }
//
//                        if (this.halt && !this.finishStream) {
//                            break;
//                        }
//                    }
//                } catch (Throwable var17) {
//                    this.thrown = var17;
//                } finally {
//                    this.halt = true;
//                    if (this.closeInput) {
//                        try {
//                            this.in.close();
//                        } catch (IOException var16) {
//                        }
//                    }
//
//                    if (this.closeOutput) {
//                        try {
//                            this.out.close();
//                        } catch (IOException var15) {
//                        }
//                    }
//
//                    this.done = true;
//                    this.completing(this.totalWritten, this.thrown);
//                }
//
//            }
//        }
//
//        public boolean halt(boolean wait, boolean finishStream) {
//            if (!this.halt) {
//                this.halt = true;
//            }
//
//            if (wait) {
//                while(!this.done) {
//                    synchronized(this) {
//                        this.notifyAll();
//                    }
//
//                    if (!this.done) {
//                        try {
//                            Thread.sleep(5L);
//                        } catch (InterruptedException var6) {
//                            break;
//                        }
//                    }
//                }
//            }
//
//            return this.halt;
//        }
//
//        public long totalWritten() {
//            return this.totalWritten;
//        }
//
//        public Throwable getThrown() {
//            return this.thrown;
//        }
//
//        protected void completing(long totalWritten, Throwable thrown) {
//        }
//    }

    public static void writeStringArray(String[] a, DataOutputStream s) throws IOException {
        if (a == null) {
            s.writeInt(0);
        } else {
            int len = a.length;
            s.writeInt(len);

            for(int i = 0; i < len; ++i) {
                s.writeUTF(a[i]);
            }

        }
    }

    public static void writeIntArray(int[] a, DataOutputStream s) throws IOException {
        int len = a.length;
        s.writeInt(len);

        for(int i = 0; i < len; ++i) {
            s.writeInt(a[i]);
        }

    }

//    public static String writeAsString(File file, String contents) {
//        LangUtil.throwIaxIfNull(file, "file");
//        if (null == contents) {
//            contents = "";
//        }
//
//        FileWriter out = null;
//
//        String var4;
//        try {
//            File parentDir = file.getParentFile();
//            if (!parentDir.exists() && !parentDir.mkdirs()) {
//                var4 = "unable to make parent dir for " + file;
//                return var4;
//            }
//
//            Reader in = new StringReader(contents);
//            out = new FileWriter(file);
//            copyStream((Reader)in, (Writer)out);
//            Object var5 = null;
//            return (String)var5;
//        } catch (IOException var16) {
//            var4 = LangUtil.unqualifiedClassName(var16) + " writing " + file + ": " + var16.getMessage();
//        } finally {
//            if (null != out) {
//                try {
//                    out.close();
//                } catch (IOException var15) {
//                }
//            }
//
//        }
//
//        return var4;
//    }

    public static void copyStream(Reader in, Writer out) throws IOException {
        boolean MAX = true;
        char[] buf = new char[4096];

        for(int bytesRead = in.read(buf, 0, 4096); bytesRead != -1; bytesRead = in.read(buf, 0, 4096)) {
            out.write(buf, 0, bytesRead);
        }

    }

    public static String[] readStringArray(DataInputStream s) throws IOException {
        int len = s.readInt();
        String[] ret = new String[len];

        for(int i = 0; i < len; ++i) {
            ret[i] = s.readUTF();
        }

        return ret;
    }

    public static boolean[] readBooleanArray(DataInputStream s) throws IOException {
        int len = s.readInt();
        boolean[] ret = new boolean[len];

        for(int i = 0; i < len; ++i) {
            ret[i] = s.readBoolean();
        }

        return ret;
    }

    public static void writeBooleanArray(boolean[] a, DataOutputStream s) throws IOException {
        int len = a.length;
        s.writeInt(len);

        for(int i = 0; i < len; ++i) {
            s.writeBoolean(a[i]);
        }

    }

    public static int[] readIntArray(DataInputStream s) throws IOException {
        int len = s.readInt();
        int[] ret = new int[len];

        for(int i = 0; i < len; ++i) {
            ret[i] = s.readInt();
        }

        return ret;
    }


    public static byte[] write(Serializable object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
        stream.writeObject(object);
        return byteArrayOutputStream.toByteArray();
    }

    public static Object read(byte[] data) throws IOException, ClassNotFoundException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
        ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);
        return inputStream.readObject();
    }

}
